﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;

namespace Utility {
    public class ImageHelper {

        public static Bitmap Grayscale(Bitmap source) {
            int w = source.Width;
            int h = source.Height;
            BitmapData sourceData = source.LockBits(
                new Rectangle(0, 0, w, h),
                ImageLockMode.ReadOnly,
                PixelFormat.Format24bppRgb);

            Bitmap dest = new Bitmap(w, h, PixelFormat.Format8bppIndexed);

            BitmapData destData = dest.LockBits(
                new Rectangle(0, 0, w, h),
                ImageLockMode.WriteOnly,
                PixelFormat.Format8bppIndexed);

            unsafe {
                byte* srcPtr = (byte*)sourceData.Scan0;
                byte* destPtr = (byte*)destData.Scan0;

                int srcOffset = sourceData.Stride - 3 * w;
                int destOffset = destData.Stride - w;
                for (int y = 0; y < h; y++) {
                    for (int x = 0; x < w; x++) {
                        byte r, g, b;
                        b = *(srcPtr++);
                        g = *(srcPtr++);
                        r = *(srcPtr++);
                        *(destPtr++) = (byte)((19595 * r + 38469 * g + 7472 * b) >> 16);
                    }
                    srcPtr += srcOffset;
                    destPtr += destOffset;
                }
            }

            source.UnlockBits(sourceData);
            dest.UnlockBits(destData);

            ColorPalette palette = dest.Palette;
            for (int i = 0; i < 256; i++) {
                palette.Entries[i] = Color.FromArgb(i, i, i);
            }
            dest.Palette = palette;
            return dest;
        }

        public static void GetHist(Bitmap gray, out int[] hist) {
            if (gray == null)
                throw new ArgumentNullException("gray");

            BitmapData srcData = gray.LockBits(
                new Rectangle(0, 0, gray.Width, gray.Height),
                ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);

            hist = new int[256];
            unsafe {
                byte* ptr = (byte*)srcData.Scan0;
                int offset = srcData.Stride - srcData.Width;
                for (int y = 0; y < gray.Height; y++) {
                    for (int x = 0; x < gray.Width; x++, ptr++) {
                        hist[*ptr]++;
                    }
                    ptr += offset;
                }
            }

            gray.UnlockBits(srcData);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="gray">A grayscale image.</param>
        /// <returns>The max between-class-var.</returns>
        public static int CalculateThreshold(Bitmap gray) {

            if (gray == null)
                throw new ArgumentNullException("gray");

            int w = gray.Width;
            int h = gray.Height;

            int[] hist;
            GetHist(gray, out hist);

            double[] proHist = new double[256];
            int threshold = 0;

            double pixelCount = w * h;
            double imageMean = 0;
            for (int i = 0; i < 256; i++) {
                proHist[i] = (double)hist[i] / pixelCount;
                imageMean += (double)i * proHist[i];
            }

            double class1Mean = 0;
            double class2Mean = imageMean;
            double class1Pro = 0.0;
            double class2Pro = 1.0;

            double max = double.MinValue;
            for (int t = 0; t < 256; t++) {
                double betweenClassVar =
                    class1Pro * class2Pro * Math.Pow(class1Mean - class2Mean, 2);

                if (betweenClassVar > max) {
                    max = betweenClassVar;
                    threshold = t;
                }

                class1Mean *= class1Pro;

                class1Pro += proHist[t];
                class2Pro -= proHist[t];

                if (class2Pro == 0) {
                    break;
                }

                class1Mean += (double)t * proHist[t];

                if (class1Pro != 0) {
                    class1Mean /= class1Pro;
                }

                class2Mean = (imageMean - class1Mean * class1Pro) / class2Pro;
            }


            return threshold;
        }

        public static Bitmap Binarization(Bitmap gray, byte threshold) {
            if (gray == null)
                throw new ArgumentNullException("gray");

            int w = gray.Width;
            int h = gray.Height;

            BitmapData srcData = gray.LockBits(
                new Rectangle(0, 0, w, h),
                ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);

            Bitmap dest = new Bitmap(w, h, PixelFormat.Format8bppIndexed);

            BitmapData destData = dest.LockBits(
                new Rectangle(0, 0, w, h),
                ImageLockMode.WriteOnly,
                PixelFormat.Format8bppIndexed);

            unsafe {
                byte* srcPtr = (byte*)srcData.Scan0;
                byte* destPtr = (byte*)destData.Scan0;

                int srcOffset = srcData.Stride - w;
                int destOffset = destData.Stride - w;

                for (int y = 0; y < h; y++) {
                    for (int x = 0; x < w; x++) {
                        *(destPtr++) = *(srcPtr++) >= threshold ? (byte)255 : (byte)0;
                    }
                    destPtr += destOffset;
                    srcPtr += srcOffset;
                }
            }

            gray.UnlockBits(srcData);
            dest.UnlockBits(destData);

            return dest;
        }

        public static void GetHistogram(Bitmap gray, byte threshold, out int[] hist) {

            if (gray == null)
                throw new ArgumentNullException("gray");

            int w = gray.Width;
            int h = gray.Height;

            BitmapData data = gray.LockBits(
                new Rectangle(0, 0, w, h),
                ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);

            hist = new int[w];
            unsafe {
                byte* ptr = (byte*)data.Scan0;
                fixed (int* p = hist) {
                    int offset = data.Stride - w;
                    int* pHist = p;
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++, pHist++, ptr++) {
                            if (*ptr < threshold) {
                                (*pHist)++;
                            }
                        }
                        pHist = p;
                        ptr += offset;
                    }
                }
            }

            gray.UnlockBits(data);
        }

        public static void DrawHistogram(int[] hist, int topY, string filename) {
            int max = topY;
            int width = 300;
            int height = 300;
            double meanHeight = 0;
            double meanWidth = 0;
            using (Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb)) {
                using (Graphics g = Graphics.FromImage(bmp)) {
                    g.FillRectangle(new SolidBrush(Color.White),
                        0, 0, width, height);
                    //for (int i = 0; i < hist.Length; i++) {
                    //    if (hist[i] > max) {
                    //        max = hist[i];
                    //    }
                    //}

                    meanHeight = (double)height / (double)max;
                    meanWidth = (double)width / (double)hist.Length;
                    Pen p = new Pen(Color.Black, (float)meanWidth);
                    float xOff = 0;
                    for (int i = 0; i < hist.Length; i++) {
                        g.DrawLine(p, xOff, (float)height - (float)(meanHeight * hist[i]), xOff, (float)height);
                        xOff += (float)meanWidth;
                    }

                    g.Save();
                }

                bmp.Save(filename, ImageFormat.Jpeg);
            }
        }

        public static void GetDataRange(Bitmap source, int xstart, int xcount, int ystart, int ycount, out int[,] data) {
            if (source == null) {
                throw new ArgumentNullException("source");
            }

            BitmapData bmpData = source.LockBits(
                new Rectangle(0, 0, source.Width, source.Height),
                ImageLockMode.ReadOnly,
                PixelFormat.Format24bppRgb);

            data = new int[ycount, xcount];
            unsafe {
                byte* ptr = (byte*)bmpData.Scan0;
                fixed (int* pData = data) {
                    int* p = pData;
                    int offset = bmpData.Stride - bmpData.Width * 3;
                    ptr += ystart * bmpData.Stride + xstart * 3;
                    for (int y = ystart; y < ystart + ycount; y++) {
                        for (int x = xstart; x < xstart + xcount; x++, p++) {
                            byte b = *(ptr++);
                            byte g = *(ptr++);
                            byte r = *(ptr++);
                            *p = b + (g << 8) + (r << 16);
                        }
                        ptr += (bmpData.Width - xstart - xcount) * 3 + offset;
                    }
                }
            }
        }
    }
}
